One Time Bad
My super secure service is available now! Heck, even with the source, I bet you won't figure it out.
- File: actf2020-bad-server.py
- Netcat:
nc misc.2020.chall.actf.co 20301
Recon
The server uses time.time()
as seed:
random.seed(int(time.time()))
We are also able to request samples of the OTP or test our luck by guessing the plaintext from a given ciphertext (so no key obviously). If we're correct, we get the flag.
The samples are tuple of a plaintext, key and ciphertext. The plaintext and key are a random string of ASCII characters with random (but equal length).
Using these samples we can try to bruteforce the seed. Actually, only one sample was needed. After we have the seed we can easily predict the next randomly generated sample.
After obtaining the seed, the first generated sample is equal to our given sample and the second one is the question for the flag.
Solution
import random, string, time, base64
p = 'qbAtjPxJBbpFqKTZjNSwjjRJtxGO'
p = [string.ascii_letters.index(c) for c in p]
lp = len(p)
print(f'Looking for length {lp} and numbers {p}')
def find_seed():
i = int(time.time())
while True:
if not (i % 100000):
print(f'Checking from {i}')
random.seed(i)
l = random.randint(1, 30)
if l == lp:
if all([random.randint(0, 51) == c for c in p]):
print(f'Found: {i}')
return i
i -= 1
def otp(a, b):
r = ""
for i, j in zip(a, b):
r += chr(ord(i) ^ ord(j))
return r
def genSample():
p = ''.join([string.ascii_letters[random.randint(0, len(string.ascii_letters)-1)] for _ in range(random.randint(1, 30))])
k = ''.join([string.ascii_letters[random.randint(0, len(string.ascii_letters)-1)] for _ in range(len(p))])
x = otp(p, k)
return x, p, k
random.seed(find_seed())
# First sample is the known p sample.
x, p, k = genSample()
# The next sample is the server's test for the flag.
x, p, k = genSample()
print(f'The server will ask for: {base64.b64encode(x.encode()).decode()}')
print(f'The answer is: {p}')
Flag
actf{one_time_pad_more_like_i_dont_like_crypto-1982309}